package org.mentalsmash.tazio.transactions.user.impl;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.util.Iterator;

import org.mentalsmash.tazio.transactions.control.TransactionStateException;
import org.mentalsmash.tazio.transactions.model.RootSet;
import org.mentalsmash.tazio.transactions.model.TransactionalObject;
import org.mentalsmash.tazio.transactions.model.identifiers.OID;
import org.mentalsmash.tazio.transactions.model.impl.TxObjectWrapper;
import org.mentalsmash.tazio.transactions.model.impl.TxObjectWrapperFactory;
import org.mentalsmash.tazio.transactions.user.RollbackException;
import org.mentalsmash.tazio.transactions.user.Share;
import org.mentalsmash.tazio.transactions.user.TransactionalInterfaceException;
import org.mentalsmash.tazio.transactions.user.TransactionalSystemRuntimeException;
import org.mentalsmash.tazio.transactions.user.UserTransactionManager;
import org.mentalsmash.tazio.transactions.user.annotations.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;

public class RootSetWrapper implements Share {

    private final static Logger log = LoggerFactory
	    .getLogger(RootSetWrapper.class);

    private final TxObjectWrapperFactory objectWrapperFactory;
    private final RootSet rootSet;

    @Inject
    public RootSetWrapper(final TxObjectWrapperFactory factory, final RootSet rs) {
	this.objectWrapperFactory = factory;
	this.rootSet = rs;
    }

    public synchronized RootSet getRootSet() {
	return this.rootSet;
    }

//    public synchronized void setRootSet(final RootSet rs) {
//	Preconditions.checkNotNull(rs, "RootSet must be not null");
//	this.rootSet = rs;
//    }

    @Override
    public <T extends Serializable> T createObject(
	    final Class<T> transactionalIface)
	    throws TransactionStateException, RollbackException {

	final boolean valid = checkTypeValidity(transactionalIface);
	if (!valid) {
	    throw new TransactionalInterfaceException(
		    "Interface {} is not proxable. Check the log for details");
	}

	final T proxiedInstance = createProxiedInstance(transactionalIface);

	getRootSet().add(proxiedInstance);

	return proxiedInstance;

    }

    /**
     * Given a suitable interface type (i.e. one who correctly implements
     * {@link Transactional} semantics), creates an instance of that interface
     * using the provided implementation.
     * 
     * @param <E>
     * 
     * @param txIface
     *            the interface to create a proxy for
     * @return an instance proxied with transactional logic
     */
    private <E> E createProxiedInstance(final Class<E> txIface) {
	Serializable instance = null;
	final TxObjectWrapper wrapper;
	final Class<?>[] interfaces; // set of interfaces for the proxy to
	// implement
	// retrieve the annotation. Already checked whether present or not.
	final Class<?> implementation = txIface.getAnnotation(
		Transactional.class).implementation();

	try {
	    instance = (Serializable) implementation.newInstance();
	} catch (final InstantiationException e) {
	    log.error("Fatal error while instantiating proxy", e);
	    throw new TransactionalSystemRuntimeException(e);
	} catch (final IllegalAccessException e) {
	    log.error("Fatal error while instantiating proxy", e);
	    throw new TransactionalSystemRuntimeException(e);
	}

	wrapper = this.objectWrapperFactory.createTxWrapper(instance);
	interfaces = new Class<?>[] { txIface, TransactionalObject.class };

	final E newProxyInstance = txIface.cast(Proxy.newProxyInstance(txIface
		.getClassLoader(), interfaces, wrapper));

	return newProxyInstance;
    }

    /**
     * Checks whether objectType is an interface correctly marked with the
     * {@link Transactional} interface, and if the provided implementation is
     * suitable for instantiation
     * 
     * @return true if it is
     */
    private boolean checkTypeValidity(final Class<?> txIface) {
	Class<?> implementationClass;

	if (!txIface.isInterface()) {
	    log.error("{} must be an interface", txIface.getSimpleName());
	    return false;
	}

	if (!txIface.isAnnotationPresent(Transactional.class)) {
	    log.error("{} is not correctly annotated with @Transactional",
		    txIface.getSimpleName());
	    return false;
	}

	implementationClass = txIface.getAnnotation(Transactional.class)
		.implementation();

	try {
	    implementationClass.getConstructor(); // gets the
	    // default
	    // constructor
	} catch (final NoSuchMethodException e) {
	    log.error("Unable to create proxy for type {}."
		    + " No default constructor available "
		    + "in its implementation", implementationClass
		    .getSimpleName(), e);
	    return false;
	} catch (final SecurityException e) {
	    log.error("Unable to create proxy for type {}."
		    + " Security exception", implementationClass
		    .getSimpleName(), e);
	    return false;
	}

	return true;
    }

    @Override
    public <T extends Serializable> T getObject(final OID objectId) {
	return (T) getRootSet().getObject(objectId);
    }

    @Override
    public Iterator<Serializable> iterator() {
	return getRootSet().iterator();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.RootSet#removeObject(java.lang.Object)
     */
    @Override
    public boolean removeObject(final Object toRemove) {
	return getRootSet().remove(toRemove);

    }

}
